Slide 1: Title Slide


Slide 2: Table of Contents

  1. Introduction to Smart Contract Security
  2. Common Vulnerabilities in Smart Contracts
    • Reentrancy Attacks
    • Integer Overflow/Underflow
    • Front-running
    • Timestamp Dependence
    • Denial of Service
  3. Tools for Smart Contract Security Analysis
    • MythX
    • Slither
    • Oyente
  4. Best Practices for Writing Secure Smart Contracts
  5. Case Studies of Notable Security Breaches and Lessons Learned

Section 1: Introduction to Smart Contract Security


Slide 3: What is a Smart Contract?


Slide 4: Importance of Security in Smart Contracts


Slide 5: Security Threat Landscape


Slide 6: Overview of Common Vulnerabilities


Section 2: Common Vulnerabilities in Smart Contracts


Slide 7: Reentrancy Attacks

contract Vulnerable {
    mapping(address => uint256) public balances;

    function withdraw(uint256 _amount) public {
        require(balances[msg.sender] >= _amount);
        msg.sender.call.value(_amount)("");
        balances[msg.sender] -= _amount;
    }
}
AttackerContractCall withdraw()Send fundsCall withdraw() (reentrant)Send funds (again)AttackerContract

Slide 8: Integer Overflow/Underflow

contract VulnerableToken {
    mapping(address => uint256) public balances;

    function transfer(address _to, uint256 _value) public {
        require(balances[msg.sender] >= _value);
        balances[msg.sender] -= _value;
        balances[_to] += _value;
    }
}
AttackerContractCall transfer() with _value = 0balances[msg.sender] -= _value (underflow)Increase balance without limitsAttackerContract

Slide 9: Front-running

contract Exchange {
    uint256 public price;

    function updatePrice(uint256 _newPrice) public {
        price = _newPrice;
    }

    function buy() public payable {
        uint256 amount = msg.value / price;
        // Transfer tokens to buyer
    }
}
AttackerExchangeObserve pending transactionFront-run transaction to buy cheaper tokensProfit from price discrepancyAttackerExchange

Slide 10: Timestamp Dependence

contract Lottery {
    function play() public payable {
        if (block.timestamp % 7 == 0) {
            // Winner logic
        }
    }
}
MinerContractMine block with controlled timestampInfluence the outcome to winMinerContract

Slide 11: Denial of Service (DoS)

contract DoS {
    function compute() public {
        while(true) {
            // Infinite loop to consume gas
        }
    }
}
AttackerContractExecute function with high gas consumptionFunction halts due to gas limitAttackerContract

Section 3: Tools for Smart Contract Security Analysis


Slide 12: Introduction to Security Analysis Tools


Slide 13: MythX

Input
Analysis
Contract Code
MythX
Security Report

Slide 14: Slither

Analyzed by
Smart Contract
Slither
Report
Actionable Insights

Slide 15: Oyente

Contract
Oyente
Vulnerability Report

Section 4: Best Practices for Writing Secure Smart Contracts


Slide 16: Follow the Principle of Least Privilege

contract SecureContract {
    address public owner;

    modifier onlyOwner() {
        require(msg.sender == owner, "Not authorized");
        _;
    }

    function changeOwner(address _newOwner) public onlyOwner {
        owner = _newOwner;
    }
}
Check
Yes
No
Function Call
Is msg.sender Owner?
Execute
Revert

Slide 17: Implement Robust Access Controls

contract MultiSigWallet {
    mapping(address => bool) public isOwner;
    uint public requiredSignatures;

    function executeTransaction() public {
        require(approvedByOwners());
        // Execute transaction
    }

    function approvedByOwners() internal view returns (bool) {
        // Check if required signatures are obtained
    }
}

Yes
No
Transaction Request
Check Signatures
Enough Signatures?
Execute Transaction
Reject Transaction

Slide 18: Use SafeMath Libraries

library SafeMath {
    function add(uint256 a, uint256 b) internal pure returns (uint256) {
        uint256 c = a + b;
        require(c >= a, "Addition overflow");
        return c;
    }
}

contract SafeContract {
    using SafeMath for uint256;
    uint256 public totalSupply;

    function mint(uint256 amount) public {
        totalSupply = totalSupply.add(amount);
    }
}
No Overflow
Overflow
Addition Operation
SafeMath Library
Check Overflow
Proceed
Revert

Slide 19: Ensure Proper Input Validation

contract Registration {
    function register(string memory username) public {
        require(bytes(username).length > 0, "Invalid username");
        // Proceed with registration
    }
}
Yes
No
Input
Validation Function
Valid Input?
Proceed
Revert

Slide 20: Adopt a Secure Development Lifecycle

Design
Development
Testing
Deployment
Monitoring

Section 5: Case Studies of Notable Security Breaches and Lessons Learned


Slide 21: The DAO Hack (2016)

AttackerDAOCall withdraw()Send fundsCall withdraw() (reentrant)Send funds (again)AttackerDAO

Slide 22: Parity Wallet Freeze (2017)

UserWalletCall destroy() functionContract destroyed, funds inaccessibleUserWallet

Slide 23: The Fomo3D Game

Yes
No
Player
Game Contract
Win Condition?
Player Wins
Player Loses

Side Notes

Checks-Effects-Interactions Pattern

The Checks-Effects-Interactions pattern is a critical best practice in smart contract development. It is designed to prevent reentrancy attacks, which are a type of vulnerability where an attacker can repeatedly call a function before previous executions are complete, potentially draining the contract’s funds.


Why Checks-Effects-Interactions?


Steps in Checks-Effects-Interactions

  1. Checks:

    • Purpose: Validate all necessary conditions before proceeding with the function’s logic.

    • Examples: Ensure that the sender has sufficient balance, that the contract is in the correct state, or that input values are valid.

    • Code Example:

require(balances[msg.sender] >= _amount, "Insufficient balance");
  1. Effects:
  1. Interactions:
(bool success, ) = msg.sender.call{value: _amount}("");
require(success, "Transfer failed");

Example: Vulnerable vs. Secure Implementation

Vulnerable Example:

contract Vulnerable {
    mapping(address => uint256) public balances;

    function withdraw(uint256 _amount) public {
        require(balances[msg.sender] >= _amount);
        // Interaction before state change (vulnerable to reentrancy)
        msg.sender.call{value: _amount}("");
        balances[msg.sender] -= _amount;
    }
}

Secure Example (Checks-Effects-Interactions):

contract Secure {
    mapping(address => uint256) public balances;

    function withdraw(uint256 _amount) public {
        require(balances[msg.sender] >= _amount, "Insufficient balance");

        // Effects: Update state before any interaction
        balances[msg.sender] -= _amount;

        // Interactions: External call after state update
        (bool success, ) = msg.sender.call{value: _amount}("");
        require(success, "Transfer failed");
    }
}

Solution: The state change (updating the balance) occurs before the external interaction (sending Ether), preventing reentrancy because the balance is reduced before the attacker can re-enter the function.

UserContractCall withdraw()Check Conditions (balances[msg.sender] >= _amount)Update State (balances[msg.sender] -= _amount)External Interaction (Transfer funds)UserContract

Commit-Reveal Schemes

Commit-reveal schemes are cryptographic protocols designed to ensure fairness and prevent cheating in situations where participants need to make secret decisions before revealing them. These schemes are widely used in decentralized applications (dApps), smart contracts, and blockchain-based voting systems to prevent front-running, collusion, and other types of manipulation.


Why Use Commit-Reveal Schemes?


How Commit-Reveal Schemes Work

Commit-reveal schemes generally involve two phases:

  1. Commit Phase:

    • Purpose: Participants submit a cryptographic commitment to their decision without revealing the actual decision.

    • Commitment: This is typically done by hashing the decision with a nonce (random value) to produce a unique hash. The hash is submitted to the contract, locking in the participant’s choice.

    • Example:

bytes32 commitment = keccak256(abi.encodePacked(decision, nonce));

Reveal Phase:

require(keccak256(abi.encodePacked(decision, nonce)) == commitment, "Invalid reveal");

Example: Simple Commit-Reveal Scheme

Commit Phase:

mapping(address => bytes32) public commitments;

function commit(bytes32 _commitment) public {
    commitments[msg.sender] = _commitment;
}

Reveal Phase:

mapping(address => uint256) public decisions;

function reveal(uint256 _decision, uint256 _nonce) public {
    bytes32 _commitment = keccak256(abi.encodePacked(_decision, _nonce));
    require(commitments[msg.sender] == _commitment, "Invalid reveal");
    decisions[msg.sender] = _decision;
}

Mermaid Diagram: Commit-Reveal Process

sequenceDiagram
    participant Participant
    participant SmartContract

    Participant->>SmartContract: Commit (hash)
    SmartContract-->>Participant: Acknowledge commitment

    Participant->>SmartContract: Reveal (decision, nonce)
    SmartContract-->>SmartContract: Verify commitment

    SmartContract-->>Participant: Accept/Reject